use sim_common::{
    endpoint,
    message::{self, FromBytes, ToBytes},
    Error, Result,
};

mod ffi;

pub struct Client {
    sender: endpoint::Sender,
    reciever: endpoint::Reciever,
    conn_id: endpoint::ConnectionId,
}

impl Client {
    pub async fn connect(uri: &str) -> Result<Self> {
        endpoint::connect(uri)
            .await
            .map(|(sender, reciever, conn_id)| Client {
                sender,
                reciever,
                conn_id,
            })
    }

    pub fn recycle(vch: VehicleControlHandle, sh: SensorHandle) -> Self {
        Self {
            sender: vch.sender,
            reciever: sh.reciever,
            conn_id: vch.conn_id,
        }
    }

    pub async fn create_vehicle(
        self,
        client_id: &str,
        vehicle_type: &str,
        position_xyz: (f64, f64, f64),
        orientation_wxyz: (f64, f64, f64, f64),
    ) -> Result<(VehicleControlHandle, SensorHandle)> {
        let req = message::UpMessage::CreateVehicle(message::CreateVehicle {
            client_id: client_id.to_string(),
            vehicle_type: vehicle_type.to_string(),
            position_xyz,
            orientation_wxyz,
        });

        self.sender
            .send(self.conn_id.clone(), req.to_bytes()?)
            .await?;

        self.vehicle_response().await
    }

    pub async fn get_vehicle(
        self,
        client_id: &str,
        token: &str,
    ) -> Result<(VehicleControlHandle, SensorHandle)> {
        let req = message::UpMessage::GetVehicle(message::GetVehicle {
            client_id: client_id.to_string(),
            token: token.to_string(),
        });

        self.sender
            .send(self.conn_id.clone(), req.to_bytes()?)
            .await?;

        self.vehicle_response().await
    }

    async fn vehicle_response(mut self) -> Result<(VehicleControlHandle, SensorHandle)> {
        loop {
            let (conn_id, payload) = self.reciever.recv().await?;
            let rep = message::DownMessage::from_bytes(&payload)?;
            if let message::DownMessage::Response(rep) = rep {
                if rep.ret {
                    return Ok((
                        VehicleControlHandle {
                            sender: self.sender,
                            conn_id,
                        },
                        SensorHandle {
                            reciever: self.reciever,
                        },
                    ));
                } else {
                    return Err(Error::new("RPC failed.".to_string()));
                }
            }
        }
    }
}

#[derive(Clone)]
pub struct VehicleControlHandle {
    sender: endpoint::Sender,
    conn_id: endpoint::ConnectionId,
}

impl VehicleControlHandle {
    pub async fn set_control(&mut self, control: &VehicleControl) -> Result<()> {
        let req = message::UpMessage::VehicleControl(control.clone());

        self.sender
            .send(self.conn_id.clone(), req.to_bytes()?)
            .await
    }

    pub async fn set_transform(
        &mut self,
        position_xyz: (f64, f64, f64),
        orientation_wxyz: (f64, f64, f64, f64),
    ) -> Result<()> {
        let req = message::UpMessage::SetVehicleTransform(message::VehicleTransform {
            position_xyz,
            orientation_wxyz,
        });

        self.sender
            .send(self.conn_id.clone(), req.to_bytes()?)
            .await
    }
}

pub type VehicleControl = message::VehicleControl;

pub struct SensorHandle {
    reciever: endpoint::Reciever,
}

impl SensorHandle {
    pub async fn get_sensors(&mut self) -> Result<Sensors> {
        loop {
            let (_, payload) = self.reciever.recv().await?;
            let msg = message::DownMessage::from_bytes(&payload[..])?;

            if let message::DownMessage::Sensors(msg) = msg {
                return Ok(msg);
            }
        }
    }
}

pub type Sensors = message::Sensors;

pub type LocalizationInfo = message::LocalizationInfo;

pub type PerceptionInfo = message::PerceptionInfo;

pub type ObstacleInfo = message::ObstacleInfo;

pub type ObstacleType = message::ObstacleType;

pub type BoundingBox = message::BoundingBox;

pub type TrafficLightInfo = message::TrafficLightInfo;

pub type TrafficLightState = message::TrafficLightState;

pub type ChassisInfo = message::ChassisInfo;

pub type CollisionEvent = message::CollisionEvent;

pub type LaneInvasionEvent = message::LaneInvasionEvent;

pub type LaneChangeType = message::LaneChangeType;

pub type Image = message::Image;

#[cfg(test)]
mod test {
    use std::{
        sync::{Arc, RwLock},
        time::Duration,
    };

    use ggez::{
        conf::{FullscreenType, ModuleConf, NumSamples, WindowMode, WindowSetup},
        event,
        graphics::{self, DrawParam, Image},
        {Context, ContextBuilder, GameError, GameResult},
    };
    use tokio::{runtime, spawn, time::sleep};

    use super::*;

    #[test]
    fn test() -> Result<()> {
        let ih: Arc<RwLock<Option<super::Image>>> = Arc::new(RwLock::new(None));

        let rt = runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .map_err(|e| Error::from(e))?;

        {
            let _guard = rt.handle().enter();

            async fn run_client(ih: Arc<RwLock<Option<super::Image>>>) -> Result<()> {
                let client = Client::connect("172.16.34.76:17000").await?;
                let (mut ch, mut sh) = client.get_vehicle("001", "hero").await?;

                spawn(async move {
                    loop {
                        let control = VehicleControl {
                            throttle: 0.25,
                            steer: 0.0,
                            brake: 0.0,
                            hand_brake: false,
                            reverse: false,
                            manual_gear_shift: true,
                            gear: 1,
                        };
                        if let Err(e) = ch.set_control(&control).await {
                            println!("{}", e);
                            break;
                        }

                        sleep(Duration::from_millis(10)).await;
                    }
                });

                loop {
                    let sensors = sh.get_sensors().await?;

                    if let Some(result) = sensors.test_result {
                        // println!("test result {}", result.success);
                    };

                    if let Some(localization) = sensors.localization {
                        // println!("localization time {}", localization.timestamp);
                        // println!(
                        //     "{} {} {}",
                        //     localization.cartesian_lla.0,
                        //     localization.cartesian_lla.1,
                        //     localization.cartesian_lla.2
                        // );
                        // println!(
                        //     "{} {} {}",
                        //     localization.linear_vel_xyz.0,
                        //     localization.linear_vel_xyz.1,
                        //     localization.linear_vel_xyz.2
                        // );
                        // println!(
                        //     "{} {} {}",
                        //     localization.linear_accel_xyz.0,
                        //     localization.linear_accel_xyz.1,
                        //     localization.linear_accel_xyz.2
                        // );
                        // println!(
                        //     "{} {} {}",
                        //     localization.angular_vel_xyz.0,
                        //     localization.angular_vel_xyz.1,
                        //     localization.angular_vel_xyz.2
                        // );
                    }

                    if let Some(perception) = sensors.perception {
                        // println!(
                        //     "perception time {}, obstacle num {}",
                        //     perception.timestamp,
                        //     perception.obstacles.len()
                        // );
                        // for o in perception.obstacles {
                        //     let p = o.position_xyz;
                        //     println!("{} {} {}", p.0, p.1, p.2);
                        // }
                    }

                    if let Some(chassis) = sensors.chassis {
                        // println!("chassis time {}", chassis.timestamp);
                    }

                    for image in sensors.camera_images {
                        // println!(
                        //     "camera {} time {}, image width {}, height {}",
                        //     image.camera_id, image.timestamp, image.width, image.height
                        // );

                        *ih.write().unwrap() = Some(image);
                    }
                }
            }

            rt.spawn(run_client(ih.clone()));
        }

        let window_width = 800;
        let window_height = 600;
        let conf = ContextBuilder::new("test", "test")
            .modules(ModuleConf {
                gamepad: false,
                audio: false,
            })
            .window_mode(WindowMode {
                width: window_width as f32,
                height: window_height as f32,
                maximized: false,
                fullscreen_type: FullscreenType::Windowed,
                borderless: false,
                min_width: window_width as f32,
                min_height: window_height as f32,
                max_width: window_width as f32,
                max_height: window_height as f32,
                resizable: false,
                visible: true,
                resize_on_scale_factor_change: false,
            })
            .window_setup(WindowSetup {
                title: "sim-client-test".to_string(),
                samples: NumSamples::One,
                vsync: true,
                icon: "".to_string(),
                srgb: true,
            });
        let (ctx, event_loop) = conf.build().map_err(|e| Error::from(e))?;
        event::run(ctx, event_loop, Game::new(ih));

        Ok(())
    }

    struct Game {
        ih: Arc<RwLock<Option<super::Image>>>,
    }

    impl Game {
        fn new(ih: Arc<RwLock<Option<super::Image>>>) -> Self {
            Self { ih }
        }
    }

    impl event::EventHandler<GameError> for Game {
        fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
            Ok(())
        }

        fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
            let image = self.ih.write().unwrap().take();
            if let Some(mut image) = image {
                image.decompress_data().map_err(|_| GameError::CustomError("Decompress image data failed".into()))?;
                let data = image.data;

                let mut buf;
                if !image.is_gray {
                    buf = vec![0u8; data.len() / 3 * 4];
                    for i in 0..buf.len() / 4 {
                        buf[i * 4] = data[i * 3 + 2];
                        buf[i * 4 + 1] = data[i * 3 + 1];
                        buf[i * 4 + 2] = data[i * 3];
                        buf[i * 4 + 3] = 255;
                    }
                } else {
                    buf = vec![0u8; data.len() * 4];
                    for i in 0..buf.len() / 4 {
                        buf[i * 4] = data[i];
                        buf[i * 4 + 1] = data[i];
                        buf[i * 4 + 2] = data[i];
                        buf[i * 4 + 3] = 255;
                    }
                }

                let image =
                    Image::from_rgba8(ctx, image.width as u16, image.height as u16, &buf[..])?;

                graphics::draw(
                    ctx,
                    &image,
                    DrawParam::default().dest(mint::Point2 { x: 0.0, y: 0.0 }),
                )?;
            }

            graphics::present(ctx)
        }
    }
}
